/**
 * @file frame.h
 * @brief Main looping frame for service.
 * @author Hongbao Chen
 * @version 1.0 Beta
 * @date 2012-04-03
 */


#ifndef MONITOR_H_
#define MONITOR_H_

#ifndef SEPOLL_H_
#include "sepoll.h"
#endif
#ifndef SSTRING_H_
#include "sstring.h"
#endif
#ifndef SSOCKET_H_
#include "ssocket.h"
#endif

#include <sys/time.h>



#ifdef __cplusplus
extern "C" {
#endif




/**
 * @brief Structure ServiceEnv keeps all the configuration for the whole servive system.
 * [note]It is vitally important.
 */
struct ServiceEnv {
	/* parameters from .ini */
	int fail_retry_sec;

	int listen_port;
	int listen_backlog;
	in_addr_t inaddr;

	int max_conn;
	int max_req_line;
	
	int snd_to_sec;
	int snd_to_usec;
	int rcv_to_sec;
	int rcv_to_usec;

	struct IniFile ini_file;
	char so_path[MAX_PATHLEN];
	
	/* parameters from runtime */
	int max_ev;

	void *pdata;
	socklen_t data_len;

	pid_t main_pid;

};

typedef struct ServiceEnv	Env;
typedef struct ServiceEnv *	pEnv;

#define pEnvNULL	(pEnv)0
#define MSGBUF_SIZE	1024

/** The default values for the essential configuration.  */
#define DEF_TO_SEC		(-1)
#define DEF_TO_USEC		(-1)

#define DEF_MAX_CONN		20480
#define DEF_MAX_REQ_LINE	4096

#define DEF_FAIL_RETRY_SEC	10

#define DEF_LISTEN_PORT		2012
#define DEF_LISTEN_ADDR		INADDR_ANY
#define DEF_LISTEN_BACKLOG	64

/** The default config keys for the conrresponding values. */
#define FRM_CFG_SO_PATH		"frame.so.path"
#define DEF_FRM_SO_PATH		"./frame.so"

/**
 * frame.handler.setup	= setup_in_so
 * frame.handler.in	= in_in_so
 *
 * setup_in_so(struct ServiceEnv*){...}
 * in_in_so(struct ServiceEnv*, int sock_fd){...}
 * */
#define FRM_CFG_HD_SETUP	"frame.handler.setup"
#define FRM_CFG_HD_IN		"frame.handler.in"

typedef void(*HD_IN)(struct ServiceEnv*, int, struct sockaddr_in);

#define FRM_CFG_PORT		"frame.listen.port"
#define FRM_CFG_ADDR		"frame.listen.addr"
#define FRM_CFG_BACKLOG		"frame.listen.backlog"

#define FRM_CFG_FAIL_RETRY_SEC	"frame.failtry"
#define FRM_CFG_MAX_CONN	"frame.maxconn"
#define FRM_CFG_MAX_REQ_LINE	"frame.maxreqline"
#define FRM_CFG_STO_SEC		"frame.sendtimeout.sec"
#define FRM_CFG_STO_USEC	"frame.sendtimeout.usec"
#define FRM_CFG_RTO_SEC		"frame.recvtimeout.sec"
#define FRM_CFG_RTO_USEC	"frame.recvtimeout.usec"


#define GLB_CFG_SO_PATH		"global.so.path"
#define DEF_GLB_SO_PATH		"./globe.so"

/**
 * global.handler.exit	= exit_in_so
 * global.handler.setup	= setup_in_so
 * [.so file]
 * exit_in_so(...){...}
 * setup_in_so(...){...}
 *
 */
#define GLB_CFG_HD_EXIT		"global.handler.exit"
#define GLB_CFG_HD_SETUP	"global.handler.setup"

typedef void(*HD_SETUP)(struct ServiceEnv*);


int buildServiceEnv( const char *_pIniPath, struct ServiceEnv *_pEnv) {
	if( _pIniPath == NULL || _pEnv == NULL) {
		return (int)-1;
	}
	
	struct IniFile ini, *pIni;
	bzero(&ini, sizeof(struct IniFile));
	pIni = &ini;

	int n = api_fetchIni( _pIniPath, &ini);
	if(n <= 0) {
		/* No .ini configuration. */
		_pEnv->fail_retry_sec	= DEF_FAIL_RETRY_SEC;
		_pEnv->listen_port	= DEF_LISTEN_PORT;
		_pEnv->inaddr		= DEF_LISTEN_ADDR;
		_pEnv->listen_backlog	= DEF_LISTEN_BACKLOG;
		_pEnv->max_conn		= DEF_MAX_CONN;
		_pEnv->max_req_line	= DEF_MAX_REQ_LINE;
		_pEnv->snd_to_sec	= DEF_TO_SEC;
		_pEnv->snd_to_usec	= DEF_TO_USEC;
		_pEnv->rcv_to_sec	= DEF_TO_SEC;
		_pEnv->rcv_to_usec	= DEF_TO_USEC;

		_pEnv->ini_file		= ini;
	} else {
	
		char *pValue;

		pValue = api_queryIni(pIni, FRM_CFG_PORT, NULL, 0);
		if( pValue == NULL) {
			_pEnv->listen_port = DEF_LISTEN_PORT;
		} else {
			_pEnv->listen_port = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_ADDR, NULL, 0);
		if( pValue == NULL) {
			_pEnv->inaddr = DEF_LISTEN_ADDR;
		} else {
			if(strcmp(pValue, "INADDR_ANY") == 0) {
				_pEnv->inaddr = INADDR_ANY;
			} else if(strcmp(pValue, "INADDR_BROADCAST") == 0) {
				_pEnv->inaddr = INADDR_BROADCAST;
			} else {
				_pEnv->inaddr = inet_addr(pValue);
				if( _pEnv->inaddr == (in_addr_t)-1) {
					_pEnv->inaddr = DEF_LISTEN_ADDR;
				}
			}
		}

		pValue = api_queryIni(pIni, FRM_CFG_BACKLOG, NULL, 0);
		if( pValue == NULL) {
			_pEnv->listen_backlog = DEF_LISTEN_BACKLOG;
		} else {
			_pEnv->listen_backlog = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_FAIL_RETRY_SEC, NULL, 0);
		if( pValue == NULL) {
			_pEnv->fail_retry_sec = DEF_FAIL_RETRY_SEC;
		} else {
			_pEnv->fail_retry_sec = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_MAX_CONN, NULL, 0);
		if( pValue == NULL) {
			_pEnv->max_conn = DEF_MAX_CONN;
		} else {
			_pEnv->max_conn = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_MAX_REQ_LINE, NULL, 0);
		if(pValue == NULL) {
			_pEnv->max_req_line = DEF_MAX_REQ_LINE;
		} else {
			_pEnv->max_req_line = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_STO_SEC, NULL, 0);
		if(pValue == NULL) {
			_pEnv->snd_to_sec = DEF_TO_SEC;
		} else {
			_pEnv->snd_to_sec = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_STO_USEC, NULL, 0);
		if(pValue == NULL) {
			_pEnv->snd_to_usec = DEF_TO_USEC; 
		} else {
			_pEnv->snd_to_usec = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_RTO_SEC, NULL, 0);
		if(pValue == NULL) {
			_pEnv->rcv_to_sec = DEF_TO_SEC;
		} else {
			_pEnv->rcv_to_sec = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_RTO_USEC, NULL, 0);
		if(pValue == NULL) {
			_pEnv->rcv_to_usec = DEF_TO_USEC; 
		} else {
			_pEnv->rcv_to_usec = atoi(pValue);
		}

		pValue = api_queryIni(pIni, FRM_CFG_SO_PATH, NULL, 0);
		if(pValue == NULL) {
			memcpy( _pEnv->so_path, DEF_FRM_SO_PATH, strlen(DEF_FRM_SO_PATH)+1);
		} else {
			memcpy( _pEnv->so_path, pValue, strlen(pValue)+1);
		}


		_pEnv->ini_file = ini;
	}

	 return (int)0;
}




int serviceFrame(int argc, char **argv, struct ServiceEnv *_pEnv) {


	struct sockaddr_in local;
	struct sockaddr_in remote;
	struct epoll_event *events;

	struct timeval sendTimeOut;
	struct timeval recvTimeOut;

	int lfd	= 0;
	int rfd	= 0;
	int nfd = 0;
	int maxev = 0;
	int epfd  = 0;
	int flags = 0;

	char *reqBuf;

	socklen_t addrlen = 0;

	/* Message buffer,etc. logging, printing and temporary use. */
	char buf[MAX_BUF_SIZE];

	if( _pEnv == NULL) {
		API_ERR_LOG("[ERROR]Service environment is not initialized.");
		return (int)-1;
	}

	while((events = (struct epoll_event *)MALLOC( _pEnv->max_conn * sizeof(struct epoll_event))) == NULL) {
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]malloc %d struct epoll_event fail.(equally %f kbytes) Try %d seconds later.", 
				_pEnv->max_conn, _pEnv->max_conn*sizeof(struct epoll_event)/1024.0, _pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	}

	while((reqBuf = (char *)MALLOC(_pEnv->max_req_line * sizeof(char))) == NULL) {
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]malloc %d chars fail.(equally %f kbytes) Try %d seconds later.", 
				_pEnv->max_req_line, _pEnv->max_req_line*sizeof(char)/1024.0, _pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	}
	
	local.sin_family	= AF_INET;
	local.sin_port		= htons( _pEnv->listen_port);
	local.sin_addr.s_addr	= htons( _pEnv->inaddr);

	sendTimeOut.tv_sec	= _pEnv->snd_to_sec;
	sendTimeOut.tv_usec	= _pEnv->snd_to_usec;
	recvTimeOut.tv_sec	= _pEnv->rcv_to_sec;
	recvTimeOut.tv_usec	= _pEnv->rcv_to_usec;

	while((lfd = tcpSocket((struct sockaddr *)&local, (socklen_t)sizeof(struct sockaddr_in), &sendTimeOut, &recvTimeOut)) == -1) {
		snprintf(buf, MAX_BUF_SIZE-1, 
				"[ERROR]tcpSocket fail.(Probably failure in socket(), bind() or setsockopt()) Try %d seconds later.", 
				_pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	}

	flags = fcntl(lfd, F_GETFL);
	while(fcntl(lfd, F_SETFL, flags|O_NONBLOCK ) == -1) {
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]fcntl to O_NONBLOCK fail. Try %d seconds later.", 
				_pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	}


	while(listen(lfd, _pEnv->listen_backlog) == -1) { 
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]listen(%d, %d) fail. Try %d seconds later.", 
				lfd, _pEnv->listen_backlog, _pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	}

	/** Log and display the information. */
	char addr_p[64];
	snprintf(buf, MAX_BUF_SIZE-1, "[INFO]Socket create successful. Port: %d, accepted address: %s\n\
\tsend timeout: %d sec,%d usec, receive timeout: %d sec, %d usec, non-block."
			,ntohs(local.sin_port), inet_ntop(AF_INET, &local.sin_addr, addr_p, 64), 
			_pEnv->snd_to_sec, _pEnv->snd_to_usec,
			_pEnv->rcv_to_sec, _pEnv->rcv_to_usec);
	buf[MAX_BUF_SIZE-1] = '\0';
	API_ERR_LOG(buf);
	API_ERR_STDOUT(buf);

	while((epfd = epollCreate(0)) == -1) { 
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]epollCreate fail. Try %d seconds later.",  
				_pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	
	}
	
	while(epollCtlAdd(epfd, lfd, EPOLLIN|EPOLLET) == -1) { 
		snprintf(buf, MAX_BUF_SIZE-1, "[ERROR]epollCtlAdd fail. Try %d seconds later.", 
				_pEnv->fail_retry_sec);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_STDOUT(buf);
		API_ERR_LOG(buf);
		sleep( _pEnv->fail_retry_sec);
	
	}

	++maxev;
	/* Copy the current number of events alive, to struct ServiceEnv. */
	_pEnv->max_ev = maxev;

	//TODO Some methods should be invoked here to perform preparation.
	//For example, we need to set up cache.
	void *handler	= NULL;
	void *setup	= NULL;
	void *in	= NULL;	
	handler = api_soLoad( _pEnv->so_path);
	
	if(handler != NULL) {
		setup	= api_soSymbol(handler, api_queryIni( &( _pEnv->ini_file), FRM_CFG_HD_SETUP, NULL, 0));
		in	= api_soSymbol(handler, api_queryIni( &(_pEnv->ini_file), FRM_CFG_HD_IN, NULL, 0));
	} else {
		snprintf(buf, MAX_BUF_SIZE-1, "[WARNING]%s not found.", _pEnv->so_path);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_LOG(buf);
	}
	if(setup != NULL) {
		((HD_SETUP)setup)( _pEnv);
	} else {
		snprintf(buf, MAX_BUF_SIZE-1, "[WARNING]%s in %s not found.", 
				api_queryIni(&(_pEnv->ini_file), FRM_CFG_HD_SETUP, NULL, 0), 
				_pEnv->so_path
				);
		buf[MAX_BUF_SIZE-1] = '\0';
		API_ERR_LOG(buf);
	}

	if(in == NULL) {
		snprintf(buf, MAX_BUF_SIZE-1, "[WARNING]%s in %s not found.",
		api_queryIni(&(_pEnv->ini_file), FRM_CFG_HD_IN, NULL, 0),
		api_queryIni(&(_pEnv->ini_file), FRM_CFG_SO_PATH, NULL, 0)
				);

		API_ERR_LOG(buf);
	}

	API_ERR_LOG("[INFO]Start service.");
	API_ERR_STDOUT("[INFO]Start service.");
	while(1) {
		if((nfd = epollWait(epfd, events, maxev, -1, NULL)) == -1) {
			API_ERR_LOG("[WARNNING]epollWait fail. Continue to wait again.");
			continue;
		}

		int i = 0;
		for(i=0; i<nfd; ++i) {
			if(IS_CONN(events,i,lfd)) {
				rfd = accept(lfd, (struct sockaddr *)&remote, &addrlen);
				if(rfd == -1) {
					API_ERR_LOG("[WARNNING]accept fail. Give up this conection.");
					/* If the current socket accept fail, just give it up and move onto te next. */
					continue;
				}

				flags = fcntl(rfd, F_GETFL);
				if(fcntl(rfd, F_GETFL, flags|O_NONBLOCK) == -1) {
					shutdown(rfd, O_RDWR);
					close(rfd);
					API_ERR_LOG("[WARNNING]fcntl after accept fail. Give up this connection.");
					continue;
				}
				if(maxev < _pEnv->max_conn) {
					if(epollCtlAdd(epfd, rfd, EPOLLIN|EPOLLET) == 0) {
						++maxev;
						_pEnv->max_ev = maxev;
					} else {

						shutdown(rfd, O_RDWR);
						close(rfd);
						API_ERR_LOG("[WARNNING]epollCtlAdd fail.(A connection can not be added to epoll_wait()).");
						continue;
					}
				} else {

					//TODO Consider what to do if the max_conn is reached.
					//Some error handling measures should be taken here.
					API_ERR_LOG("[WARNNING]ServiceEnv.max_conn is reached.(Limited number of epollWait() events).");
					continue;
				}

				//TODO add your code here to handle the socket.
				

			} else if(IS_IN(events,i)) {
				//TODO add your code here to handle the socket.
				if(in != NULL) {
					((HD_IN)in)(_pEnv, rfd, remote);
				}

			} else if(IS_OUT(events,i)) {

				//TODO add your code here to handle the socket.

			} else if(IS_OOB(events,i)) {

				//TODO add your code here to handle the socket.


			} else if(IS_MSG(events,i)) {

				//TODO add your code here to handle the socket.

			} else if(IS_HUP(events,i)) {
				//TODO add your code here to handle the socket.
				if(epollCtlDel(epfd, events[i].data.fd)!= 0) {
					snprintf(buf, MAX_BUF_SIZE-1, "[WARNING]Cannot delete fd %d from epoll.", events[i].data.fd);
					buf[MAX_BUF_SIZE-1] = '\0';
					API_ERR_LOG(buf);
				}

			} else if(IS_ERR(events,i)) {
				//TODO add your code here to handle the socket.
				if(epollCtlDel(epfd, events[i].data.fd)!= 0) {
					snprintf(buf, MAX_BUF_SIZE-1, "[WARNING]Cannot delete fd %d from epoll.", events[i].data.fd);
					buf[MAX_BUF_SIZE-1] = '\0';
					API_ERR_LOG(buf);
				}
			} 

		}

	}


	return 0;
}





#ifdef __cplusplus
}
#endif



#endif /* FRAME_H_ */

